package server;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class Server {
	
	private ServerSocket socket;
	
	boolean running = true;

	/**
	 * @param args
	 */
	public void run() {		
		running = true;
		try {
			setSocket(new ServerSocket(15676));
			System.out.println(socket.getInetAddress());
			while (running) {
				System.out.println("Afventer spiller!");
				Socket playerSocket = getSocket().accept();
				System.out.println("Ny spiller!");
				System.out.println(connect(playerSocket));
			}
			getSocket().close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static Server getInstance() {
		// No need for multithread safety as server gets created before any threads are created.
		if(instance == null) {
			instance = new Server();
		}		
		return instance;
	}
	
	public void setWelcomeMessage(String welcomeMessage) {
		this.welcomeMessage = welcomeMessage;
	}
	
	public String getWelcomeMessage() {
		return welcomeMessage;
	}
	
	private boolean connect(Socket socket) throws IOException {

		BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		DataOutputStream out = new DataOutputStream(socket.getOutputStream());

		String buffer = in.readLine();
		String[] information = buffer.split("#");

		if(Integer.parseInt(information[0]) != 1) {
			in.close();
			out.close();
			return false;
		}
		
		String name = information[1];
		
		String color = GameData.getUnqiueInstance().getNextColor();
		
		out.writeBytes("2#" + getWelcomeMessage() + "#" + color + "\n");		
		
		PlayerThread player = new PlayerThread(name, color, socket, in, out);
		players.put(information[1], player);
		
		GameData.getUnqiueInstance().addPlayer(player);
		broadcastNewPlayer(player);		
		Thread thread = new Thread(player);
		thread.start();
		sendCurrentPlayers(player);
		return true;
	}
	
	/**
	 * Sender et træk til alle spillere inklusive spilleren der bevægede sig.
	 * @param name
	 * @param position
	 * @throws IOException
	 */
	void broadcastMove(String name, Integer[] position) throws IOException {
		Collection<PlayerThread> audience = players.values();
		for(PlayerThread pt : audience) {
//			pt.getOut().writeBytes("3#move#"+name+"#" +position[0]+ "#" +position[1] + "#"+ stringify(position[2]) +"\n");
			if(!pt.addEvent("3#move#"+name+"#" +position[0]+ "#" +position[1] + "#"+ stringify(position[2]))) {
				pt.interrupt();
			}
		}
	}
	
	/**
	 * Sender en pointændring til alle spillere inklusive spilleren der ændres
	 * @param name
	 * @param points
	 * @throws IOException
	 */
	void broadcastPoints(String name, Integer points) throws IOException {
		Collection<PlayerThread> audience = players.values();
		for(PlayerThread pt : audience) {
//			pt.getOut().writeBytes("3#points#"+name+"#"+points+"\n");
			if(!pt.addEvent("3#points#"+name+"#"+points)) {
				pt.interrupt();
			}
		}
	}
	
	/**
	 * Sender en ny spillers navn og position til alle aktive klienter eksl. spilleren selv.
	 * @param player
	 * @throws IOException
	 */
	void broadcastNewPlayer(PlayerThread player) throws IOException {
		Collection<PlayerThread> audience = new HashSet<PlayerThread>(players.values());
		audience.remove(player);
		for(PlayerThread pt : audience) {
//			pt.getOut().writeBytes("3#add#"+player.getPlayerName()+"#"+9+"#"+7+"#"+"up"+"\n"); // TODO See game.player.Player()
			if(!pt.addEvent("3#add#"+player.getPlayerName()+"#"+9+"#"+7+"#"+"up"+"#0#"+player.getPlayerColor())) {
				pt.interrupt();
			}
		}
	}

	public void broadcastShot(Integer[] shooterPos, Integer[] targetPos) {
		Collection<PlayerThread> audience = new HashSet<PlayerThread>(players.values());
		
		String event = "3#shot#" + shooterPos[0] + "#" + shooterPos[1] + "#" + shooterPos[2];
		
		if(targetPos != null) {
			event += "#" + targetPos[0] + "#" + targetPos[1];
		} else {
			event += "#-1#-1";
		}
		
		for(PlayerThread pt : audience) {
			if(!pt.addEvent(event)) {
				pt.interrupt();
			}
		}
		
	}
	
	/**
	 * Sender alle spillere til en ny spiller eksl. spilleren selv.
	 * @param player
	 * @throws IOException
	 */
	void sendCurrentPlayers(PlayerThread player) throws IOException {
		Collection<PlayerThread> currentClients = new HashSet<PlayerThread>(players.values());
		currentClients.remove(player);
		for(PlayerThread pt : currentClients) {
			Integer[] pos = GameData.getUnqiueInstance().getPositions(player);
//			player.getOut().writeBytes("3#add#"+pt.getPlayerName()+"#"+pos[0]+"#"+pos[1]+"#"+pos[2]+"\n");
			if(!player.addEvent("3#add#"+pt.getPlayerName()+"#"+pos[0]+"#"+pos[1]+"#"+stringify(pos[2])+"#"+GameData.getUnqiueInstance().getPlayerpoints(pt)+"#"+pt.getPlayerColor())) {
				pt.interrupt();
			}
		}
	}
	
	void broadcastPlayerLeft(String name) {
		// TODO ikke kritisk
	}
	
	private static Server instance;
	private String welcomeMessage = "Velkommen til server";
	private Map<String, PlayerThread> players = new HashMap<String, PlayerThread>();
	
	/**
	 * Hjælpemetode der oversætter en int direction til string direction
	 * @param facing
	 * @return
	 */
	private String stringify(int facing) {
		// facing(0=up,1=right,2=down,3=left) 
		String direction = "";
		if(facing == 0) {
			direction = "up";
		} else if(facing == 1) {
			direction = "right";
		} else if(facing == 2) {
			direction = "down";
		} else if(facing == 3) {
			direction = "left";
		}
		return direction;
	}

	public ServerSocket getSocket() {
		return socket;
	}

	public void setSocket(ServerSocket socket) {
		this.socket = socket;
	}

	public void kill() {
		running = false;		
	}
}